/**

Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016.  All rights reserved.

Contact:
     SYSTAP, LLC DBA Blazegraph
     2501 Calvert ST NW #106
     Washington, DC 20008
     licenses@blazegraph.com

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
/*
 * Created on Aug 16, 2010
 */

package com.bigdata.bop;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;

/**
 * Abstract base class for mutable {@link BOp}s. Unlike {@link BOpBase}, this
 * class supports destructive mutation. This is the base class for the bigdata
 * AST nodes. ASTs are destructively rewritten by optimizers before they are
 * turned into a query plan.
 * <p>
 * <h2>Constructor patterns</h2>
 * <p>
 * {@link ModifiableBOpBase}s should define the following public constructors
 * <dl>
 * <dt>
 * <code>public <i>Class</i>(BOp[] args, Map&lt;String,Object&gt; anns)</code></dt>
 * <dd>A shallow copy constructor. This is used when initializing a {@link BOp}
 * from the caller's data or when generated a query plan from Prolog. There are
 * some exceptions to this rule. For example, {@link Constant} does not define a
 * shallow copy constructor because that would not provide a means to set the
 * constant's value.</dd>
 * <dt><code>public <i>Class</i>(<i>Class</i> src)</code></dt>
 * <dd>A copy constructor.</dd>
 * </dl>
 * 
 * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
 * @version $Id$
 */
public class ModifiableBOpBase extends CoreBaseBOp {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    /**
     * A mutable list containing the argument values (aka children) and never
     * <code>null</code>.
     */
    private final ArrayList<BOp> args;

    /**
     * A mutable map containing the operator annotations and never
     * <code>null</code>.
     */
    private final Map<String, Object> annotations;

    /**
     * Deep copy constructor (required).
     * <p>
     * Each {@link BOp} MUST implement a public copy constructor with the
     * signature:
     * 
     * <pre>
     * public Foo(Foo)
     * </pre>
     * 
     * This construct is invoked by {@link #clone()} using reflection.
     * <p>
     * The default implementation makes a shallow copy of {@link #args()} and
     * {@link #annotations()} but DOES NOT perform field-by-field copying.
     * Subclasses may simply delegate the constructor to their super class
     * unless they have additional fields which need to be copied.
     * 
     * @param op
     *            A deep copy will be made of this {@link BOp}.
     * 
     * @throws NullPointerException
     *             if the argument is <code>null</code>.
     */
    public ModifiableBOpBase(final ModifiableBOpBase op) {

        args = new ArrayList<BOp>(op.args);

        annotations = new LinkedHashMap<String, Object>(op.annotations);

    }

    /**
     * Shallow copy constructor (required).
     * 
     * @param args
     *            The arguments to the operator.
     * @param annotations
     *            The annotations for the operator (optional). When
     *            <code>null</code>, a mutable map with a default capacity of
     *            {@value CoreBaseBOp#DEFAULT_INITIAL_CAPACITY} is allocated. Do
     *            NOT specify an immutable map.
     */
    public ModifiableBOpBase(final BOp[] args,
            final Map<String, Object> annotations) {

        if (args == null)
            throw new IllegalArgumentException();

        checkArgs(args);

        this.args = new ArrayList<BOp>(args.length);

        for(BOp t : args)
            this.args.add(t);

        this.annotations = (annotations == null ? new LinkedHashMap<String, Object>(
                DEFAULT_INITIAL_CAPACITY) : annotations);

    }

    final public Map<String, Object> annotations() {

        return annotations;
    
    }
    
    @Override
    protected boolean annotationsEqual(final BOp o) {

        if (o instanceof ModifiableBOpBase) {

            // Fast path when comparing two modifiable bops.
            return annotationsEqual(annotations,
                    ((ModifiableBOpBase) o).annotations);

        }

        return super.annotationsEqual(annotations, o.annotations());

    }

    public BOp get(final int index) {
        
        return args.get(index);
        
    }
    
    /**
     * Return the index of the bop in the args.  Returns -1 if bop is not
     * present in the args.
     */
    public int indexOf(final BOp bop) {
    	
    	return args.indexOf(bop);
    	
    }

    /**
     * Replace the arguments.
     * 
     * @param args
     *            The new arguments.
     * @return <i>this</i>.
     */
    public ModifiableBOpBase setArgs(final BOp[] args) {
        if (args == null)
            throw new IllegalArgumentException();
        this.args.clear(); // FIXME Override might not catch clear of parent ref.
        for (BOp arg : args) {
            addArg(arg);
        }
        mutation();
        return this;
    }
    
    /**
     * Replace the value of the argument at the specified index (core mutation
     * method).
     * 
     * @param index
     *            The index of the child expression to be replaced.
     * @param newArg
     *            The new child expression.
     * 
     * @return This {@link ModifiableBOpBase}.
     * 
     * @throws IllegalArgumentException
     *             if the argument is <code>null</code>.
     * @throws IllegalArgumentException
     *             if the argument is <i>this</i>.
     */
    public ModifiableBOpBase setArg(final int index, final BOp newArg) {

        if (newArg == null)
            throw new IllegalArgumentException();

        if(newArg == this)
            throw new IllegalArgumentException();

        args.set(index, newArg);

        mutation();
        
        return this;

    }

    /**
     * Add a new argument (core mutation method).
     * 
     * @param newArg
     *            The argument.
     * 
     * @return <i>this</i>.
     * 
     * @throws IllegalArgumentException
     *             if the argument is <code>null</code>.
     * @throws IllegalArgumentException
     *             if the argument is <i>this</i>.
     */
    public void addArg(final BOp newArg) {
        
        if(newArg == null)
            throw new IllegalArgumentException();

        if(newArg == this)
            throw new IllegalArgumentException();
        
        args.add(newArg);
        
        mutation();
        
    }

    /**
     * Add a new argument (core mutation method) at the specified index.
     * 
     * @param index
     *            The index at which the child expression is to be inserted.
     * @param newArg
     *            The argument.
     * 
     * @return <i>this</i>.
     * 
     * @throws IllegalArgumentException
     *             if the argument is <code>null</code>.
     * @throws IllegalArgumentException
     *             if the argument is <i>this</i>.
     */
    public void addArg(final int index, final BOp newArg) {
        
        if(newArg == null)
            throw new IllegalArgumentException();

        if(newArg == this)
            throw new IllegalArgumentException();
        
        args.add(index, newArg);
        
        mutation();
        
    }

    /**
     * Add an argument iff it is not already present.
     * 
     * @param arg
     *            The argument.
     *            
     * @throws IllegalArgumentException
     *             if the argument is <code>null</code>.
     * @throws IllegalArgumentException
     *             if the argument is <i>this</i>.
     */
    public void addArgIfAbsent(final BOp arg) {

        if(arg == null)
            throw new IllegalArgumentException();

        if(arg == this)
            throw new IllegalArgumentException();

        if(!args.contains(arg)) {

            addArg(arg);
         
        }
        
    }
    
    /**
     * Remove the 1st occurrence of the argument (core mutation method).
     * 
     * @param arg
     *            The argument.
     *            
     * @return <code>true</code> iff the argument was removed.
     * 
     * @throws IllegalArgumentException
     *             if the argument is <code>null</code>.
     * @throws IllegalArgumentException
     *             if the argument is <i>this</i>.
     */
    public boolean removeArg(final BOp arg) {
        
        if (arg == null)
            throw new IllegalArgumentException();

        if (arg == this)
            throw new IllegalArgumentException();

        if (args.remove(arg)) {
         
            mutation();
            
            return true;
            
        }

        return false;
        
    }
    
    /**
     * Replace a child of a node with another reference (destructive
     * modification). All arguments which point to the oldChild will be replaced
     * by references to the newChild.
     * 
     * @param oldChild
     * @param newChild
     * 
     * @return The #of references which were replaced.
     */
    public int replaceWith(final BOp oldChild, final BOp newChild) {

        final ModifiableBOpBase p = this;

        final int arity = p.arity();

        int nmods = 0;

        for (int i = 0; i < arity; i++) {

            final BOp child = p.get(i);

            if (child == oldChild) {

                ((ModifiableBOpBase) p).setArg(i, newChild);

                nmods++;

            }

        }

        return nmods;

    }
    
    public int arity() {
        
        return args.size();
        
    }

    /**
     * An unmodifiable view of the list of arguments (aka children) of this
     * node.
     * <p>
     * Note: The view is not modifiable in order to preserve the contract that
     * {@link #mutation()} will be invoked if there is change in the state of
     * this {@link BOp}.
     */
    final public List<BOp> args() {

        return new NotifyingList<BOp>(this);
        
    }
    
    /**
     * Class provides notice on mutation events.
     */
    static private class NotifyingList<T> implements List<T> {
        
        private final ModifiableBOpBase bop;
        
        private final List<T> delegate;
        
        @SuppressWarnings("unchecked")
        NotifyingList(final ModifiableBOpBase bop) {
            this(bop, (List<T>) bop.args);
        }

        NotifyingList(final ModifiableBOpBase bop, final List<T> subList) {
            this.bop = bop;
            this.delegate = subList;
        }

        private boolean mutation(boolean modified) {
            if(modified) {
                bop.mutation();
            }
            return modified;
        }
        public int size() {
            return delegate.size();
        }
        public boolean isEmpty() {
            return delegate.isEmpty();
        }
        public boolean contains(Object o) {
            return delegate.contains(o);
        }

        public Iterator<T> iterator() {
            return new NotifyingListIterator<T>(bop, delegate.listIterator());
        }
        public Object[] toArray() {
            return delegate.toArray();
        }
        public <T1> T1[] toArray(T1[] a) {
            return delegate.toArray(a);
        }
        public boolean add(T e) {
            return mutation(delegate.add(e));
        }
        public boolean remove(Object o) {
            return mutation(delegate.remove(o));
        }
        public boolean containsAll(Collection<?> c) {
            return delegate.containsAll(c);
        }
        public boolean addAll(Collection<? extends T> c) {
            return mutation (delegate.addAll(c));
        }
        public boolean addAll(int index, Collection<? extends T> c) {
            return mutation(delegate.addAll(index, c));
        }
        public boolean removeAll(Collection<?> c) {
            return mutation(delegate.removeAll(c));
        }
        public boolean retainAll(Collection<?> c) {
            return mutation(delegate.retainAll(c));
        }
        public void clear() {
            delegate.clear();
            bop.mutation();
        }
        public boolean equals(Object o) {
            return delegate.equals(o);
        }
        public int hashCode() {
            return delegate.hashCode();
        }
        public T get(int index) {
            return delegate.get(index);
        }
        public T set(int index, T element) {
            final T ret = delegate.set(index, element);
            bop.mutation();
            return ret;
        }
        public void add(int index, T element) {
            delegate.add(index, element);
            bop.mutation();
        }
        public T remove(int index) {
            final T ret = delegate.remove(index);
            bop.mutation();
            return ret;
        }
        public int indexOf(Object o) {
            return delegate.indexOf(o);
        }
        public int lastIndexOf(Object o) {
            return delegate.lastIndexOf(o);
        }
        public ListIterator<T> listIterator() {
            return new NotifyingListIterator<T>(bop, delegate.listIterator());
        }
        public ListIterator<T> listIterator(int index) {
            return new NotifyingListIterator<T>(bop, delegate.listIterator(index));
        }

        public List<T> subList(int fromIndex, int toIndex) {
            return new NotifyingList<T>(bop, delegate.subList(fromIndex,
                    toIndex));
        }

    }

    /**
     * Iterator with mutation notify pattern.
     */
    private static class NotifyingListIterator<T> implements ListIterator<T> {

        private final ModifiableBOpBase bop;

        private final ListIterator<T> delegate;

        NotifyingListIterator(ModifiableBOpBase bop,
                ListIterator<T> listIterator) {
            this.bop = bop;
            this.delegate = listIterator;
        }

        public boolean hasNext() {
            return delegate.hasNext();
        }

        public T next() {
            return delegate.next();
        }

        public boolean hasPrevious() {
            return delegate.hasPrevious();
        }

        public T previous() {
            return delegate.previous();
        }

        public int nextIndex() {
            return delegate.nextIndex();
        }

        public int previousIndex() {
            return delegate.previousIndex();
        }

        public void remove() {
            delegate.remove();
            bop.mutation();
        }

        public void set(T e) {
            delegate.set(e);
            bop.mutation();
        }

        public void add(T e) {
            delegate.add(e);
            bop.mutation();
        }
        
    }

    /**
     * {@inheritDoc}
     * <p>
     * Note: This {@link Iterator} supports removal.
     */
    final public Iterator<BOp> argIterator() {
        
        return new NotifyingList<BOp>(this, args).iterator();

    }

    // shallow copy
    public BOp[] toArray() {

        return args.toArray(new BOp[args.size()]);

    }

    // shallow copy
    public <T> T[] toArray(final T[] a) {

        return args.toArray(a);
        
    }

//    @SuppressWarnings("unchecked")
//    public <T> T getProperty(final String name) {
//
//        return (T) annotations.get(name);
//
//    }

    public Object getProperty(final String name) {

        return annotations.get(name);

    }

//    public <T> T getRequiredProperty(final String name) {
//
//        @SuppressWarnings("unchecked")
//        final T tmp = (T) annotations.get(name);
//
//        if (tmp == null)
//            throw new IllegalArgumentException("Required property: " + name);
//
//        return tmp;
//        
//    }
    
    /**
     * Copy all annotations from the caller's map.
     * 
     * @param anns
     *            The annotations to be copied.
     * 
     * @return <i>this</i>
     */
    public ModifiableBOpBase copyAll(final Map<String, Object> anns) {

        annotations.putAll(anns);

        return this;

    }

    /**
     * Set the named property value (destructive mutation).
     * 
     * @param name
     *            The name.
     * @param value
     *            The new value.
     *            
     * @return <i>this</i>.
     */
    public ModifiableBOpBase setProperty(final String name, final Object value) {

        if (value == null) {
            if (annotations.remove(name) != null) {
                mutation();
            }
        } else {
            annotations.put(name, value);
            mutation();
        }

        return this;
        
    }

    /**
     * Conditionally set the named property iff it is not bound (destructive
     * mutation).
     * 
     * @param name
     *            The name.
     * @param value
     *            The value.
     * 
     * @return <i>this</i>.
     * 
     * @throws IllegalStateException
     *             if the property is already set.
     */
    public ModifiableBOpBase setUnboundProperty(final String name, final Object value) {

        final Object oldValue = getProperty(name);
        
        if (oldValue != null)
            throw new IllegalStateException("Already set: name=" + name
                    + ", value=" + oldValue);

        setProperty(name, value);

        return this;

    }

    /**
     * Clear the named annotation (destructive mutation).
     * 
     * @param name
     *            The annotation.
     *            
     * @return <i>this</i>.
     */
    public ModifiableBOpBase clearProperty(final String name) {

        if (name == null)
            throw new IllegalArgumentException();

        if (annotations.remove(name) != null) {

            mutation();
            
        }
        
        return this;

    }

    /**
     * Unconditionally set the {@link Annotations#BOP_ID}.
     * 
     * @param id
     *            The id.
     */
    public void setId(final int id) {

        setProperty(Annotations.BOP_ID, id);
        
    }
    
//    public ModifiableBOpBase deepCopy() {
//        
//        final ModifiableBOpBase bop = (ModifiableBOpBase) this.clone();
//
//        deepCopyArgs(bop.args);
//        
//        deepCopyAnnotations(bop.annotations);
//        
//        return bop;
//        
//    }
//    
//    /**
//     * Deep copy the arguments.
//     * <p>
//     * Note: As long as we stick to the immutable semantics for bops, we can
//     * just make a shallow copy of the arguments in the "copy" constructor and
//     * then modify them within the specific operator constructor before
//     * returning control to the caller. This would result in less heap churn.
//     */
//    private void deepCopyArgs() {
//        final ArrayList<BOp> tmp = new ArrayList<BOp>(args);
//        
//        if (a == BOp.NOARGS || a.length == 0) {
//            // fast path for zero arity operators.
//            return BOp.NOARGS;
//        }
//        final BOp[] t = new BOp[a.length];
//        for (int i = 0; i < a.length; i++) {
//            t[i] = a[i] == null ? null : a[i].clone();
//        }
//        return t;
//    }
//
//    /**
//     * Deep copy the annotations.
//     * <p>
//     * Note: This does not know how to deep copy annotations which are not
//     * {@link BOp}s or immutable objects such as {@link String}s or
//     * {@link Number}s. Such objects should not be used as annotations.
//     * 
//     * @todo When attaching large data sets to a query plan they should be
//     *       attached using a light weight reference object which allows them to
//     *       be demanded by a node so deep copy remains a light weight
//     *       operation. This also has the advantage that the objects are
//     *       materialized on a node only when they are needed, which keeps the
//     *       query plan small. Examples would be sending a temporary graph
//     *       containing an ontology or some conditional assertions with a query
//     *       plan.
//     */
//    static protected Map<String, Object> deepCopy(final Map<String, Object> a) {
//        if (a == BOp.NOANNS) {
//            // Fast past for immutable, empty annotations.
//            return a;
//        }
//        // allocate map.
//        final Map<String, Object> t = new LinkedHashMap<String, Object>(a
//                .size());
//        // copy map's entries.
//        final Iterator<Map.Entry<String, Object>> itr = a.entrySet().iterator();
//        while (itr.hasNext()) {
//            final Map.Entry<String, Object> e = itr.next();
//            if (e.getValue() instanceof BOp) {
//                // deep copy bop annotations.
//                t.put(e.getKey(), ((BOp) e.getValue()).clone());
//            } else {
//                // shallow copy anything else.
//                t.put(e.getKey(), e.getValue());
//            }
//        }
//        // return the copy.
//        return t;
//    }

}
